<template>
  <el-form
    ref="formRef"
    :model="formData"
    :rules="formRules"
    label-width="120px"
  >
    <el-row :gutter="20">
      <el-col
        v-for="field in processedFields"
        :key="field.name"
        :span="field.span || 24"
      >
        <el-form-item :label="field.label" :prop="field.name">
          <!-- 动态组件 -->
          <component
            :is="componentMap[field.type]"
            v-model="formData[field.name]"
            v-bind="componentProps(field)"
          >
            <!-- 处理选项型组件 -->
            <template v-if="hasOptions(field.type)">
              <component
                :is="
                  isOptionComponentType(field.type)
                    ? optionComponentMap[field.type]
                    : null
                "
                v-for="opt in field.options"
                :key="opt.value"
                :label="opt.label"
                :value="opt.value"
              >
                {{ opt.label }}
              </component>
            </template>
          </component>
        </el-form-item>
      </el-col>
    </el-row>
  </el-form>
</template>

<script setup lang="ts">
import { ref, reactive, computed, watch, defineExpose } from "vue";
import type { FormInstance } from "element-plus";
import { cloneDeep } from "lodash-es";
import {
  ElInput,
  ElSelect,
  ElOption,
  ElRadioGroup,
  ElRadio,
  ElCheckboxGroup,
  ElCheckbox,
  ElTimePicker,
  ElDatePicker,
} from "element-plus";

type Option = {
  label: string;
  value: string;
};

export type Field = {
  type: keyof typeof componentMap; // Restrict type to keys of componentMap
  label: string; // 标签
  name: string; // 字段名
  span?: number; // 栅格宽度
  options?: Option[]; // select radio checkbox 选项
  attrs?: object; // 附加属性，
  rules?: object[]; // 验证规则
  dependencies?: string[]; // 依赖关系
};

const props = defineProps({
  // 表单配置项
  fields: {
    type: Array as () => Field[],
    required: true,
    validator: (val: Field[]) =>
      val.every(
        (f) =>
          f.name &&
          f.label &&
          ["input", "select", "radio", "checkbox", "date"].includes(f.type)
      ),
  },
  // 初始值
  formData: {
    type: Object,
    default: () => ({}),
  },
});

// 组件映射配置
const componentMap = {
  input: ElInput,
  select: ElSelect,
  radio: ElRadioGroup,
  checkbox: ElCheckboxGroup,
  timepicker: ElTimePicker,
  datepicker: ElDatePicker,
};

const optionComponentMap = {
  select: ElOption,
  radio: ElRadio,
  checkbox: ElCheckbox,
};

// 表单实例
const formRef = ref<FormInstance | null>(null);

// 表单数据（深拷贝初始值）
const formData = reactive(cloneDeep(props.formData));

// 处理后的字段配置
const processedFields = computed(() =>
  props.fields.map((field) => ({
    ...field,
  }))
);

// 表单验证规则
const formRules = computed(() => {
  const rules = {};
  props.fields.forEach((field: Field) => {
    if (field.rules) {
      Object.defineProperty(rules, field.name, {
        value: cloneDeep(field.rules),
      });
    }
  });
  return rules;
});

// 获取组件额外属性
const componentProps = (field: Field) => {
  if (field.attrs) {
    const props = cloneDeep(field.attrs);
    return props;
  }
};

// 类型守卫函数
const isOptionComponentType = (
  type: string
): type is "select" | "radio" | "checkbox" => {
  return ["select", "radio", "checkbox"].includes(type);
};
// 判断是否需要选项
const hasOptions = (type: keyof typeof componentMap) =>
  ["select", "radio", "checkbox"].includes(type);

const emit = defineEmits(["update:value", "submit"]);

// 自动监听依赖变化
props.fields.forEach((field) => {
  if (field.dependencies) {
    watch(
      () => field.dependencies?.map((dep) => formData[dep]),
      () => {
        // 触发字段更新
        formData[field.name] = Array.isArray(formData[field.name]) ? [] : null;
      },
      { deep: true }
    );
  }
});

// 暴露给父组件的方法
defineExpose({
  // 验证表单
  validate: () => formRef.value?.validate(),
  // 重置表单
  reset: () => {
    formRef.value?.resetFields();
    Object.assign(formData, cloneDeep(props.formData));
  },
  // 获取表单数据
  getFormData: () => cloneDeep(formData),
});

// 数据同步
watch(
  formData,
  (val) => {
    emit("update:value", cloneDeep(val));
  },
  { deep: true }
);
</script>
